C++ template

您所在的位置:网站首页 template typename t 用法 C++ template

C++ template

2023-06-26 03:08| 来源: 网络整理| 查看: 265

《C++ Template》对Template各个方面进行了较为深度详细的解析,故而本系列博客按书本的各章顺序编排,并只作为简单的读书笔记,详细讲解请购买原版书籍(绝对物超所值)。------------------------------------------------------------------------------------------------------------第一章 前言1.4 编程风格(1)对“常整数”趋向使用“int const”,而不是使用“const int”。“恒定不变部分”指的是const限定符前面的部分。------------------------------------------------------------------------------------------------------------第1部分 基础第2章 函数模板2.1 初探函数模板2.1.1 定义模板

template inline T const& max(T const& a, T const& b) { return a < b ? : b a; }

注:你可以使用任何类型(基本类型、类等)来实例化该类型参数,只要所用类型提供模板使用的操作就可以。2.1.2 使用模板

max(32, 43);

这种用具体类型代替模板参数的过程叫做实例化,它产生了一个模板的实例。

注:通常而言,并不是把模板编译成一个可以处理任何类型的单一实体;而是对于实例化模板参数的每种类型,都从模板产生一个不同的实体。

于是,我们可以得出一个结论:模板被编译了两次,分别发生在:(1)实例化之前,先检查模板代码本身,查看语法是否正确;在这里会发现错误的语法,如遗漏分号等。(2)在实例化期间,检查模板代码,查看是否所有的调用都有效。在这里会发现无效的调用,如该实例化类型不支持某些函数调用(该类型没有提供模板所需要使用到的操作)等。

2.2 实参的演绎(deduction)

注:模板实参不允许进行自动类型转换;每个T都必须正确地匹配。如:

max(4, 4.3); // Error:第1个参数类型是int,第2个参数类型是double

2.3 模板参数函数模板有两种类型的参数(牢记):(1)模板参数:位于函数模板名称的前面,在一对尖括号内部进行声明:

template // T是模板参数

(2)调用参数:位于函数模板名称之后,在一对圆括号内部进行声明:

...max(T const& a, T const& b); // a和b都是调用参数

注:由于函数模板历史发展过程中的一个失误,导致目前(2016/1/11)我们不能在函数模板内部指定缺省的模板实参(形如“template ”,不能指定xxx;)。但依然可以指定函数模板的调用实参(形如“...max(T const& a, T const& b = yyy)”,可以指定yyy)(以后应该会支持函数模板指定缺省模板实参)。

注:切记“模板参数”和“模板实参”的区别;函数的“模板参数”和“调用参数”、“模板实参”和“调用实参”的区别;“类型参数”和“非类型参数”的区别[参见下面第4章内容];

函数模板和类模板区别【重要】:

函数模板可以进行模板实参演绎(不能演绎返回类型)、重载、指定缺省调用实参、全局特化;不能指定缺省模板实参,不能局部特化; 类模板可以指定缺省模板实参、指定全局特化和局部特化(用来完成类似函数模板重载功能);不能重载类模板,不能进行实参演绎。

(3)显式实例化:当模板参数和调用参数没有发生关联,或者不能由调用参数来决定模板参数的时候(例如:返回类型),在调用时就必须显式指定模板实参。切记,模板实参演绎并不适合返回类型。如下:

template inline RT max(T1 const& a, T2 const& b);

那么必须进行显式实例化:

max(4, 4.3); // OK,但很麻烦。这里T1和T2是不同的类型,所以可以指定两个不同类型的实参4和4.3

注:通常而言,你必须指定“最后一个不能被隐式演绎的模板实参之前的”所有实参类型。上面的例子中,改变模板参数的声明顺序,那么调用者就只需要指定返回类型:

template inline RT max(T1 const& a, T2 const& b); ... max(4, 4.3); // ok,返回类型是double

2.4 重载函数模板

注:对于非模板函数和同名的函数模板,如果其他条件都是相同的话,那么在调用的时候,重载解析过程通常会优先调用非模板函数,而不会从该模板产生出一个实例。然而,如果模板可以产生一个具有更好匹配的函数,那么将选择模板。 注:可以显式地指定一个空的模板实参列表,这个语法好像是告诉编译器:只有模板才能匹配这个调用(即便非模板函数更符合匹配条件也不会被调用到),而且所有的模板参数都应该根据调用实参演绎出来。 max(7, 42); // call max(通过实参演绎) 注:因为模板是不允许自动类型转化的;但普通函数可以进行自动类型转换,所以当一个匹配既没有非模板函数,也没有函数模板可以匹配到的时候,会尝试通过自动类型转换调用到非模板函数(前提是可以转换为非模板函数的参数类型)。 注:在所有重载的实现里面,我们都是通过引用来传递每个实参的。一般而言,在重载函数模板的时候,最好只是改变那些需要改变的内容;就是说,你应该把你的改变限制在下面两种情况:改变参数的数目或者显式地指定模板参数。否则可能会出现非预期的结果。

(需要参考书本2.4节p17,p18例子,重点研究如下的传参方式):

// 求两个指针所指向值的最大者 // T* const& a :表示 T类型的指针常量a(a指针的指向不可修改),并且指针常量a是通过引用方式传参;// 如果修改为 T* const a :表示 T类型的指针常量a,并且指针常量a通过传值的方式传参,那么会在内存栈中创建指针常量a的一个临时副本(假设叫做pa),pa和a都指向同一个内存地址; // 甚至可修改为 T*& a : 表示 T类型的指针变量a(a指针的指向可以修改),并且指针变量a通过引用方式传参;template inline T* const& max(T* const& a, T* const& b) { return *a < *b ? b : a; } 注:一定要让函数模板的所有重载版本的声明都位于它们被调用的位置之前。例如,定义一个重载函数A,而在A1(函数A的重载)中调用A,但是,如果直到A1的定义处还没有见到A的定义(也即函数A的定义在函数A1的后面,但函数A1中调用了函数A),那么并不会调用到这个重载函数A,而会寻找在函数A1之前已经定义了的符合条件的其他函数Ax(即便A是符合条件的非模板函数,而Ax是模板函数,也会由于A的声明太迟,而选择调用Ax)。

------------------------------------------------------------------------------------------------------------第3章 类模板3.1 类模板Stack的实现见书源码;

3.1.1 类模板的声明

template //可以使用class代替typename class Stack { ... };

注:这个类的类型是Stack,其中T是模板参数。因此,当在声明中需要使用该类的类型时,你必须使用Stack。然而,当使用类名而不是类的类型时,就应该只用Stack;譬如,当你指定类的名称、类的构造函数、析构函数时,就应该使用Stack。

3.1.2 成员函数的实现为了定义类模板的成员函数,你必须指定该成员函数是一个函数模板,而且你还需要使用这个类模板的完整类型限定符。如下:

template void Stack::push(T const& elem) { elems.push_back(elem); }

显然,对于类模板的任何成员函数,你都可以把它实现为内联函数,将它定义于类声明里面,如:

template class Stack { ... void push(T const& elem) // 隐式内联 { elems.push_back(elem); } ... };

3.2 类模板Stack的使用

为了使用类模板对象,必须显式地指定模板实参。

Stack intStack; // 必须显式指定模板实参int 1. 只有那些被调用的成员函数,才会产生这些函数的实例化代码。对于类模板,成员函数只有在被使用的时候才会被实例化。显然,这样可以节省空间和时间; 2. 另一个好处是,对于那些“未能提供所有成员函数中所有操作的”类型,你也可以使用该类型来实例化类模板,只要对那些“未能提供某些操作的”成员函数,模板内部不使用就可以。 3. 如果类模板中含有静态成员,那么用来实例化的每种类型,都会实例化这些静态成员。 切记,要作为模板参数类型,唯一的要求就是:该类型必须提供被调用的所有操作。

3.3 类模板的特化语法:为了特化一个类模板,你必须在起始处声明一个template,接下来声明用来特化类模板的类型。这个类型被用作模板实参,且必须在类名的后面直接指定:

template class Stack { ... };

进行类模板的特化时,每个成员函数都必须重新定义为普通函数,原来模板函数中的每个T也相应地被进行特化的类型取代。如:

void Stack::push(std::string const& elem) { elems.push_back(elem); }

3.4 局部特化(偏特化)

例子如下:

// 两个模板参数具有相同的类型 template class Myclass // { }; // 第2个模板参数的类型是int template class Myclass { }; // 也是一种模板特化,两个模板参数都是指针类型 template class Myclass // 也可以使引用类型T&,常引用等 { };

3.5 缺省模板实参对于类模板,你还可以为模板参数定义缺省值;这些值就被称为缺省模板实参;而且它们还可以引用之前的模板参数。(STL容器使用缺省默认实参指定内存分配其alloc)如:

template class Stack { };

 

------------------------------------------------------------------------------------------------------------第4章 非类型模板参数4.1 非类型的类模板参数如下定义:

// 非类型的类模板参数 int MAXSIZE :这个模板参数接受的不是一个类型,而是一个值(非类型)template class Stack { }; // 使用,第2个模板实参传入的不是类型,而是一个具体的值 Stack int20Stack; // 可以存储20个int元素的栈 Stack int40Stack; // 可以存储40个int元素的栈

注:每个模板实例都具有自己的类型,因此int20Stack和int40Stack属于不同的类型,而且这两种类型之间也不存在显式或者隐式的类型转换;所以它们之间不能互相替换,更不能互相赋值。然而,如果从优化设计的观点来看,这个例子并不适合使用缺省值。缺省值应该是直观上正确的值。但对于栈的类型和大小而言,int类型和最大容量100从直观上看起来都不是正确的。因此,在这里最好还是让程序员显式地指定这两个值。因此我们可以在设计文档中用一条声明来说明这两个属性(即类型和最大容量)。

4.2 非类型的函数模板参数如下定义:

template T addValue(T const& x) { return x + VAL; }

借助标准模板库(STL)使用上面例子:

std::transform(source.begin(), source.end(), dest.begin(), addValue);

注:

1. 上面的调用中,最后一个实参实例化了函数模板addValue(),它让int元素增加5. 2. 这个例子有一个问题:addValue是一个函数模板实例,而函数模板实例通常被看成是用来命名一组重载函数的集合(即使该组只有一个函数)。然而,根据现今的标准,重载函数的集合并不能被用于模板参数的演绎(注意,标准模板库中的函数是使用模板定义的,故而在transform()函数中,参数是作为函数模板调用实参传递的,也即参与了模板参数演绎)。于是,你必须将这个函数模板的实参强制类型转换为具体的类型: std::transform(source.begin(), source.end(), dest.begin(), (int(*)(int const&))addValue);

4.3 非类型模板参数的限制我们还应该知道:非类型模板参数是有限制的。通常而言,它们可以是常整数(包括枚举值)或者指向外部链接对象的指针,你不能使用浮点数、class类型的对象和内部链接对象(例如string)作为实参;

名称链接性的介绍参见:存储持续性、作用域和链接性  参见:8.2 模板参数_8.2.2 非类型参数

注:

之所以不能使用浮点数(包括简单的常量浮点表达式)作为模板实参是有历史原因的。然而以后可能会支持这个特性。 另外,由于字符串文字是内部链接对象(因为两个具有相同名称但出于不同模块的字符串,是两个完全不同的对象),所以你不能使用它们来作为模板实参,如下: template class MyClass { }; MyClass x; // ERROR:不允许使用字符文字"hello"////////////////////////////////////////////////////////// //另外,你也不能使用全局指针作为模板参数: template class MyClass { ... }; char const* s = "hello"; MyClass x; // ERROR:s是一个指向内部链接对象的指针/////////////////////////////////////////////////////////// //然而,你可以这样使用: template class MyClass { ... }; extern char const s[] = "hello"; MyClass x; // OK //全局字符数组s由“hello”初始化,是一个外部链接对象。

------------------------------------------------------------------------------------------------------------第5章 技巧性基础知识5.1 关键字typename在C++标准化过程中,引入关键字typename是为了说明:模板内部的标识符可以是一个类型。如下:

template class MyClass { // 这里的typename被用来说明:T::SubType是定义于类T内部的一种类型 typename T::SubType* ptr; ... };

注:本节同时提到了一个奇特的构造“.template”:只有当该前面存在依赖于模板参数的对象时,我们才需要在模板内部使用“.template”标记(和类似的诸如->template的标记),而且这些标记也只能在模板中才能使用。如下例子:

void printBitset(std::bitset const& bs) { // 如果没有使用这个template,编译器将不知道下列事实:bs.template后面的小于号(:foo()内,在基类函数调用动作之前加上“this->";[修改调用形式为:this->exit();] Base :明白指出被调用函数位于基类;[修改调用形式为:Base::exit();] 使用using:如果不想重复使用Base限定名称,可以使用using来告诉编译器被调用函数位于基类;[增加语句:using Base::exit; 之后就可以在foo()内直接调用了] 注意:如果原来的非依赖型名称是被用于虚函数调用的话,那么类似Base这种引入依赖性的限定会禁止虚函数调用,可能会导致错误; 当形如Base的方式不适用时,可以使用this->。实际上,我们更趋向于在允许使用this->前缀的地方都使用this->前缀,这同样适用于非模板代码; 对于模板化基类内的名称处理,可以参见博文Effective C++ —— 模板与泛型编程(七) 条款43 学习处理模板化基类内的名称;

5.3 成员模板对于类模板而言,其实例化只有在类型完全相同才能相互赋值。我们通过定义一个身为模板的赋值运算符(成员模板),来达到两个不同类型(但类型可以转换)的实例进行相互赋值的目的,如下声明:

template class Stack { ...  // 成员函数模板的模板参数T2和Stack模板的模板参数T不同,表示2个不同类型的相互赋值(支持隐式转换) template Stack& operator= (Stack const&); };

参见博文Effective C++ —— 模板与泛型编程(七) 条款45 运用成员函数模板接受所有兼容类型

5.4 模板的模板参数还是以Stack为例:

template   // 为模板的模板参数提供模板实参缺省值std::deque class Stack { ...  // 我们还必须对成员函数的声明进行相应的修改,如push函数的实现如下:  template   void Stack::push (T const& elem)  {    elems.push_back(elem);   // 附加传入元素的拷贝  } };

注:

1.  上面作为模板参数里面的class 不能用typename代替(这里CONT是为了定义一个类,因此只能使用关键字class); 2.  还有一个要知道:函数模板并不支持模板的模板参数; 3. 之所以需要定义“ALLOC”,是因为模板的模板实参“std::deque”具有一个缺省模板参数,为了精确匹配模板的模板参数; 4. 在使用时,第2个参数必须是一个类模板,并且由第一个模板参数T传递进来的类型进行实例化:CONT elems; 一般地,你可以使用类模板内部的任何类型来实例化模板的模板参数;

5.5 零初始化对于int、double或者指针等基本类型,并不存在“用一个有用的缺省值来对它们进行初始化”的缺省构造函数;相反,任何未被初始化的局部变量都具有一个不确定值。如果我们希望我们的模板类型的变量都已经用缺省值初始化完毕,那么针对内建类型,我们需要做一些处理,如下:

// 函数模板 template void foo() { T x = T(); // 如果T是内建类型,x是0或者false }; // 类模板:初始化列表来初始化模板成员 template class MyClass { private: T x; public: MyClass() : x() {} // 确认x已被初始化,内建类型对象也是如此 };

5.6 使用字符串作为函数模板的实参有时,把字符串传递给函数模板的引用参数会导致出人意料的运行结果:

#include // 注意,method1:引用参数 template inline T const& max(T const& a, T const& b) { return a < b ? b : a; } // method2:非引用参数 template inline T max2(T a, T b) { return a < b ? b : a; } int main() { std::string s; // 引用参数 ::max("apple", "peach"); // OK, 相同类型的实参 ::max("apple", "tomato"); // ERROR, 不同类型的实参 ::max("apple", s); // ERROR, 不同类型的实参 // 非引用参数 ::max2("apple", "peach"); // OK, 相同类型的实参 ::max2("apple", "tomato"); // OK, 退化(decay)为相同类型的实参 ::max2("apple", s); // ERROR, 不同类型的实参 }

上面method1的问题在于:由于长度的区别,这些字符串属于不同的数值类型。也就是说,“apple”和“peach”具有相同的类型char const[6];然而“tomato”的类型则是char const[7]。method2调用正确的原因是:对于非引用类型的参数,在实参演绎的过程中,会出现数组到指针的类型转换(这种转型通常也被称为decay)。小结:如果你遇到一个关于字符数组和字符指针之间不匹配的问题,你会意外地发现和这个问题会有一定的相似之处。这个问题并没有通用的解决方法,根据不同情况,你可以:

1. 使用非引用参数,取代引用参数(然而,这可能会导致无用的拷贝); 2. 进行重载,编写接收引用参数和非引用参数的两个重载函数(然而,这可能会导致二义性); 3. 对具体类型进行重载(譬如对std::string进行重载); 4. 重载数组类型,譬如: template T const* max (T const (&a)[N], T const (&b)[M]) { return a < b ? b : a; }

5. 强制要求应用程序程序员使用显式类型转换。对于我们的例子,最好的方法是为字符串重载max()。无论如何,为字符串提供重载都是必要的,否则比较的将是两个字符串的地址。

 

------------------------------------------------------------------------------------------------------------第6章 模板实战6.1 包含模型6.1.1 连接器错误

大多数C和C++程序员会这样组织他们的非模板代码:

1. 类(class)和其他类型(other type)都被放在一个头文件中。通常而言,头文件是一个扩展名为.hpp(或者.H, .h, .hh, hxx)的文件; 2. 对于全局变量和(非内联)函数,只有声明放在头文件中,定义则位于dot-C文件。通常而言,dot-C文件是指扩展名为.cpp(或者.C, .c, .cc, .cxx)的文件。

这样一切都可以正常运作了。所需的类型定义在整个程序中都是可见的;并且对于变量和函数而言,链接器也不会给出重复定义的错误。但这种情况在模板中会出现一些问题,如下:

// ----------------------------------------------------------- //basics/myfirst.hpp #ifndef MYFIRST_HPP #define MYFIRST_HPP // 模板声明 template void print_typeof(T const&) #endif // MYFIRST_HPP // ----------------------------------------------------------- //basics/myfirst.cpp// 提供了“应该实例化哪个模板定义”,但没有提供“要基于哪个模板实参来进行实例化”的说明,因为模板实参只存在myfirshmain.cpp中; #include #include #include "myfirst.hpp" // 模板的实现/定义 template void print_typeof(T const& x) { std::cout


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3